// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#pragma once

#include <memory>
#include <string>

#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>

#include "coin2/app/driver.pb.h"
#include "coin2/controller/controller.h"
#include "coin2/exchange/base/executor/archive_executor.h"
#include "coin2/exchange/base/executor/live_executor.h"
#include "coin2/exchange/base/order/subsystem_manager.h"
#include "coin2/exchange/base/symbology/product_holder_manager.h"
#include "coin2/exchange/di/constant.h"
#include "coin2/feed/mgr/system.h"
#include "coin2/strategy/manager.h"

namespace coin2::strategy {
class IStrategy;
}

namespace coin2::app {

namespace impl {

using coin2::controller::Controller;
using coin2::exchange::base::executor::ArchiveSyncExecutor;
using coin2::exchange::base::executor::LiveAsyncExecutor;
using coin2::strategy::IStrategy;

// managers
using coin2::exchange::base::order::OrderSystemManager;
using coin2::exchange::base::symbology::ProductHolderManager;
using coin2::feed::mgr::FeedSubsystemManager;
using coin2::strategy::StrategySystemManager;

class Driver {
 public:
  static void OnSignal(int signum);
  static void OnSignalImpl(int signum, bool is_live);
  static void RedirectLogToFile(const std::string&, int, bool, bool);

 public:
  Driver(
      DriverConfig config,
      IStrategy* strategy,
      exchange::di::ConstantInjectionBuilder ci_builder = exchange::di::ConstantInjectionBuilder());
  void Run(bool verbose = true);

  const DriverConfig& config() { return config_; }
  const exchange::di::ConstantInjection& GetConstants() const { return ci_; }

  LiveAsyncExecutor* live_executor() { return &live_executor_; }
  ArchiveSyncExecutor* archive_executor() { return &archive_executor_; }

  FeedSubsystemManager* feed() { return &feed_; }
  StrategySystemManager* strategy() { return &strategy_mgr_; }
  OrderSystemManager* order() { return &order_; }
  ProductHolderManager* product_holder_manager() { return &product_holder_manager_; }

  void RegisterCleanupHandler() {
    CHECK(driver_for_signal_ == nullptr) << "duplicated cleanup register";
    driver_for_signal_ = this;
    signal(SIGTERM, Driver::OnSignal);
    signal(SIGKILL, Driver::OnSignal);
    signal(SIGINT, Driver::OnSignal);
  }

  void RegisterCleanupTimeout(int64_t auto_shutdown_sec);

  void GetHealthReport(int indent, std::string* report) const;
  void LogHealthReport();

  void PostTask(std::function<void(void)>);
  void PostDelayedTask(std::function<void(void)>, int64_t delay);
  void PostPeriodicTask(std::function<void(void)>, int64_t period, int64_t clock = 100'000'000LL);
  void SetImmediateExitOnSignal(bool);
  void Exit();

 private:
  const coin2::exchange::di::ConstantInjection ci_;
  const DriverConfig config_;

  LiveAsyncExecutor live_executor_;
  ArchiveSyncExecutor archive_executor_;

  IStrategy* strategy_;
  ProductHolderManager product_holder_manager_;
  FeedSubsystemManager feed_;
  StrategySystemManager strategy_mgr_;
  OrderSystemManager order_;
  std::unique_ptr<Controller> controller_;

  std::unique_ptr<boost::asio::deadline_timer> health_report_timer_;
  std::unique_ptr<boost::asio::deadline_timer> cleanup_timer_;
  std::unique_ptr<boost::asio::deadline_timer> delayed_task_timer_;

  std::function<void(void)> exitfunc_;
  bool immediate_exit_on_signal_{true};
  std::shared_ptr<Scheduler> scheduler_;
  static Driver* driver_for_signal_;
  static bool is_handling_signal_;
};

}  // namespace impl
using impl::Driver;
DriverConfig& ConsolidateDriverConfig(
    const exchange::di::ConstantInjection& ci,
    DriverConfig* driver);

}  // namespace coin2::app
